﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Odn.Infrastructure.DependencyInjection
{
    /// <summary>
    ///     Maps an attribute to the type it decorates.
    /// </summary>
    /// <typeparam name="T">The type of attribute.</typeparam>
    public class AttributeInfo<T>
    {
        /// <summary>An attribute retrieved from a type descriptor.</summary>
        public T Attribute { get; set; }

        /// <summary>The particular type the attribute was describing.</summary>
        public Type DecoratedType { get; set; }
    }

    /// <summary>
    ///     Registers service in the Nop inversion of container upon start.
    /// </summary>
    public class DependencyAttributeRegistrator
    {
        private readonly IEngine _engine;
        private readonly ITypeFinder _finder;

        public DependencyAttributeRegistrator(ITypeFinder finder, IEngine engine)
        {
            _finder = finder;
            _engine = engine;
        }

        public virtual IEnumerable<AttributeInfo<DependencyAttribute>> FindServices()
        {
            foreach (Type type in _finder.FindClassesOfType<object>())
            {
                object[] attributes = type.GetCustomAttributes(typeof (DependencyAttribute), false);
                foreach (DependencyAttribute attribute in attributes)
                {
                    yield return new AttributeInfo<DependencyAttribute> {Attribute = attribute, DecoratedType = type};
                }
            }
        }

        public virtual void RegisterServices(IEnumerable<AttributeInfo<DependencyAttribute>> services)
        {
            foreach (var info in services)
            {
                info.Attribute.RegisterService(info, _engine.ContainerManager);
            }
        }

        public virtual IEnumerable<AttributeInfo<DependencyAttribute>> FilterServices(
            IEnumerable<AttributeInfo<DependencyAttribute>> services, params string[] configurationKeys)
        {
            return
                services.Where(
                    s => s.Attribute.Configuration == null || configurationKeys.Contains(s.Attribute.Configuration));
        }
    }

    /// <summary>
    ///     Markes a service that is registered in automatically registered in Nop's inversion of control container.
    /// </summary>
    [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
    public class DependencyAttribute : Attribute
    {
        public DependencyAttribute(ComponentLifeStyle lifeStyle = ComponentLifeStyle.Singleton)
        {
            LifeStyle = lifeStyle;
        }

        public DependencyAttribute(Type serviceType, ComponentLifeStyle lifeStyle = ComponentLifeStyle.Singleton)
        {
            LifeStyle = lifeStyle;
            ServiceType = serviceType;
        }

        /// <summary>The type of service the attributed class represents.</summary>
        public Type ServiceType { get; protected set; }

        public ComponentLifeStyle LifeStyle { get; protected set; }

        /// <summary>Optional key to associate with the service.</summary>
        public string Key { get; set; }

        /// <summary>Configurations for which this service is registered.</summary>
        public string Configuration { get; set; }

        public virtual void RegisterService(AttributeInfo<DependencyAttribute> attributeInfo, ContainerManager container)
        {
            Type serviceType = attributeInfo.Attribute.ServiceType ?? attributeInfo.DecoratedType;
            container.AddComponent(serviceType, attributeInfo.DecoratedType,
                attributeInfo.Attribute.Key ?? attributeInfo.DecoratedType.FullName, attributeInfo.Attribute.LifeStyle);
        }
    }
}